package util;

import java.util.ArrayList;

import main.Generator;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.NotFoundException;

/**
 * Class Serializer permet de créer les
 *         corps des méthodes serialize et deserialize.
 * 
 * @author Pratik Bhavsar, Fabrice Reynaud.
 */
public class Serializer {

	public ClassPool cp;
	public CtClass myClass;

	/**
	 * Contient le corps de la méthode serialize
	 */
	public String serialization;

	/**
	 * Contient le corps de la méthode deserialize
	 */
	public String deserialization;
	/**
	 * Contient l'ensemble des CtClass chargées
	 */
	public static ArrayList<CtClass> ctclasses;

	/**
	 * Constructeur
	 * 
	 * @param c : CtClass dans lequel ajouter les méthodes serialize et
	 * deserialize
	 */
	public Serializer(CtClass c) {
		this.cp = ClassPool.getDefault();
		this.myClass = c;
		this.serialization = "";
		this.deserialization = "";
	}

	/**
	 * Creation du corps de la méthode serialize
	 */
	public void createSerializeMethod() {

		// mettre d'abord le numero de la classe
		serialization += "\"" + (ctclasses.indexOf(myClass) + 1) + ";\" + ";
		// recuperer l'ensemble des champs
		CtField[] fields = myClass.getFields();

		// mettre à jour le nombre de champs max dans Generator
		if (Generator.stats) {
			int fieldSize = fields.length;

			if (fieldSize > Generator.maxField)
				Generator.maxField = fieldSize;
		}

		// initialiser la valeur de length à 0
		if (Generator.length)
			Generator.nbString = 0;

		for (CtField cf : fields) {

			String name = cf.getName();
			CtClass type = null;

			try {
				type = cf.getType();
			} catch (NotFoundException e) {
				System.out.println("Type not found.");
				continue;
			}

			String typeName = type.getSimpleName();

			if (type.isPrimitive()) {

				String line = "\"" + typeName + ":\" + this." + name
						+ " + \";\" + ";

				if (Generator.length)
					System.out.println("+ Length : Champ de taille "
							+ line.toCharArray().length * 2 + " octets");

				serialization += line;

			} else if (typeName.equals("String")) {

				if (Generator.length)
					Generator.nbString++;

				serialization += "\"String:\" + this." + name
						+ ".length() + \":\" + this." + name + " + \";\" + ";
			} else {

				String line = "this." + name + ".serialize()" + " + ";

				if (Generator.length)
					System.out.println("+ Length : Champ de taille "
							+ line.toCharArray().length * 2 + " octets");

				serialization += line;
			}
		}

		//si l'option length est à vrai, afficher le nombre de champs chaine de caractères
		if (Generator.length)
			System.out.println("+ Length : Nombre de champ de type String = "
					+ Generator.nbString);
	}

	/**
	 * Creation du corps de la méthode deserialize
	 */
	public void createDeserializeMethod() {

		
		deserialization += "\tjava.util.StringTokenizer objectString = new java.util.StringTokenizer($1, \";\");\n";
		deserialization += "\tobjectString.nextToken();\n";
		deserialization += "\tDispatcher dispatcher = new Dispatcher();\n";

		deserialization += "\tString field = \"\";\n";
		deserialization += "\tjava.util.StringTokenizer fieldString = null;\n";

		deserialization += "\tString temp = \"\";\n";

		CtField[] fields = myClass.getFields();

		for (CtField cf : fields) {
			String fieldName = cf.getName();

			CtClass type = null;

			// prend une partie du StringTokenizer, elle correspond
			// au champs cf
			deserialization += "\n";
			deserialization += "\tif(temp.isEmpty())\n";
			deserialization += "\t\tfield = objectString.nextToken();\n";
			deserialization += "\telse\n";
			deserialization += "\t\tfield = temp;\n\n";

			// prend le type de cf
			try {
				type = cf.getType();
				deserialization += "\t" + type.getName() + " _" + fieldName
						+ ";\n";
			} catch (NotFoundException e) {
				System.out.println("Type not found.");
				continue;
			}

			String typeName = type.getSimpleName();

			// En fonction du type de cf
			if (type.isPrimitive()) {

				deserialization += "\tfieldString = new java.util.StringTokenizer(field, \":\");\n";
				deserialization += "\tfieldString.nextToken();\n";

				if (typeName.equals("double"))
					deserialization += "\t_"
							+ fieldName
							+ "= Double.parseDouble(fieldString.nextToken());\n";

				if (typeName.equals("float"))
					deserialization += "\t_" + fieldName
							+ "= Double.parseFloat(fieldString.nextToken());\n";

				if (typeName.equals("int"))
					deserialization += "\t_" + fieldName
							+ "= Integer.parseInt(fieldString.nextToken());\n";

			} else if (typeName.equals("String")) {

				deserialization += "\tfieldString = new java.util.StringTokenizer(field, \":\");\n";

				deserialization += "\tfieldString.nextToken();\n";
				deserialization += "\tfieldString.nextToken();\n";

				deserialization += "\t_" + fieldName
						+ "= fieldString.nextToken();\n";

			} else {

				// check if in the ctclasses list Point has a son
				if (hasDaughter(type.getName())) {
					deserialization += "\tfield+=\";\";\n";
					deserialization += "\twhile (objectString.hasMoreTokens())\n";
					deserialization += "\t{\n";
					deserialization += "\t\ttemp = objectString.nextToken();\n";
					deserialization += "\t\tif ((temp.indexOf(\":\") > 0))\n";
					deserialization += "\t\t{\n";
					deserialization += "\t\t\tfield+=temp+\";\";\n";
					deserialization += "\t\t}else\n";
					deserialization += "\t\t\tbreak;\n";
					deserialization += "\t}\n";

					deserialization += "\t_" + fieldName + " = ("
							+ type.getName() + ")"
							+ "dispatcher.type(field);\n";

				} else {
					deserialization += "\t\tString serialiazed2 = \"\";\n";
					int nbFields = fields.length;
					for (int i = 0; i < nbFields; i++)
						deserialization += "\t\tserialiazed2+=objectString.nextToken()+\";\";\n";

					deserialization += "\t\t_" + fieldName + " = ("
							+ type.getName() + ")" + type.getName()
							+ ".deserialize(\"" + type.getName()
							+ ";\"+serialiazed2);\n";

				}

			}
		}

		// creer une instance avec les paramètres
		deserialization += "\treturn new " + myClass.getName() + "(";

		int fieldCount = myClass.getFields().length;
		for (int i = 0; i < fieldCount - 1; i++) {
			deserialization += "_" + fields[i].getName() + ",";

		}
		deserialization += "_" + fields[fieldCount - 1].getName() + ");\n";

	}

	/**
	 * Cherche s'il existe une classe fille de la class portant le nom mother
	 * 
	 * @param mother
	 *            : classe mère
	 * @return vrai s'il existe une classe fille de la class portant le nom
	 *         mother
	 */
	public static boolean hasDaughter(String mother) {

		CtClass ctclassMother = null;

		try {
			// récuperer la classe mère
			ctclassMother = ClassPool.getDefault().getCtClass(mother);
		} catch (NotFoundException e) {
			e.printStackTrace();
		}

		for (CtClass c : ctclasses)
			if (c.subclassOf(ctclassMother) && !c.equals(ctclassMother))
				return true;

		return false;
	}
}
